#!/user/bin/env python
# -*- coding: utf-8 -*-
''' 
Author: Sjy
LastEditTime: 2024-12-02 10:43:53
Description: 自定义table 组件，可以根据模板能自动生成表格数据
'''

__all__= []
import sys
import uuid
import json
import copy
from PyQt5 import QtCore, QtGui, QtWidgets

try:
    from .ui_custom_form import UiCustomForm
except:
    from ui_custom_form import UiCustomForm

global SHOW_DLGS
SHOW_DLGS = {}


class CacheDb:
    def __init__(self):
        self.__ids = {}
    def _get(self, id:str=None):
        return self.__ids.get(id)
    def _set(self, id, item):
        self.__ids[id] = item
    def _clear(self):
        self.__ids = {}
    def _init_db(self, items:list=[]):
        # 数据必须要深度拷贝的 否则会引起未知问题
        items = copy.deepcopy(items)
        ids = []
        for item in items:
            id = str(uuid.uuid4())
            self.__ids[id] = item
            ids.append(id)
        return ids

class BaseTable(QtWidgets.QTableWidget):
    """基础表 
    > 封装表格基础接口 
    """
    def __init__(self, parent:QtWidgets.QWidget=None):
        super().__init__(parent)
        # 如果不设置此样式会导致 冻结表格中的子表出现偏差，因此如果单独使用表格的需要在外部加上 frame 1px 的边框
        self.setStyleSheet("QTableWidget {padding: 0px;margin:0px;border:0px;}")#设置表格样式
    # 整体 设置 ========================================
    def _set_selection_behavior(self, behavior:str=None) -> None:
        """设置选择行为，按行选择，按列选择，按单元格选择，不选择
        Args:
            behavior (QtWidgets, optional): 选择具体行为. Defaults to QtWidgets.QAbstractItemView.SelectRows.
        Returns:
            None
        """
        if behavior == "rows":
            behavior = QtWidgets.QAbstractItemView.SelectRows
        elif behavior == "cols":
            behavior = QtWidgets.QAbstractItemView.SelectColumns
        elif behavior == "cells":
            behavior = QtWidgets.QAbstractItemView.SelectRows | QtWidgets.QAbstractItemView.SelectColumns
        elif behavior == "none":
            behavior = QtWidgets.QAbstractItemView.NoSelection
        else:
            behavior = QtWidgets.QAbstractItemView.SelectItems
        self.setSelectionBehavior(behavior)
    def _set_selection_mode(self, mode:str="single") -> None:
        """设置选择模式，单选，多选，扩展选择，连续选择
        Args:
            mode (str, optional): 选择模式. Defaults to QtWidgets.QAbstractItemView.SingleSelection.
        Returns:
            None
        """
        if mode == "single":# 只能选择一个项目
            mode = QtWidgets.QAbstractItemView.SingleSelection
        elif mode == "multi": # 可以选择多个项目，但是不可以进行连续选择。
            mode = QtWidgets.QAbstractItemView.MultiSelection
        elif mode == "extended": # 可以进行连续选择，即按住 Shift 键选择多个连续的项目。
            mode = QtWidgets.QAbstractItemView.ExtendedSelection
        elif mode == "contiguous": # 可以进行扩展选择，即按住 Ctrl 或 Shift 键选择多个项目，可以不连续。
            mode = QtWidgets.QAbstractItemView.ContiguousSelection
        else: # 不允许选择任何模式
            mode = QtWidgets.QAbstractItemView.NoSelection
        self.setSelectionMode(mode)
    def _set_edit_triggers(self, triggers:str=None) -> None:
        """设置编辑触发器,缺省不允许触发
        Args:
            triggers (str, optional): 触发的类型. Defaults to None.
        Returns:
            None
        """
        if triggers == "current":
            triggers = QtWidgets.QAbstractItemView.CurrentChanged
        elif triggers == "double":
            triggers = QtWidgets.QAbstractItemView.DoubleClicked
        elif triggers == "selected":
            triggers = QtWidgets.QAbstractItemView.SelectedClicked
        elif triggers == "edit":
            triggers = QtWidgets.QAbstractItemView.EditKeyPressed
        elif triggers == "any":
            triggers = QtWidgets.QAbstractItemView.AnyKeyPressed
        elif triggers == "all":
            triggers = QtWidgets.QAbstractItemView.AllEditTriggers
        else:
            triggers = QtWidgets.QAbstractItemView.NoEditTriggers
        self.setEditTriggers(triggers)
    # header 设置 ========================================
    def _set_header_highlight(self, highlight:bool=None, orientation:str="horizontal") -> None:
        """设置编辑触发器,缺省不允许触发
        Args:
            highlight (bool, optional): . Defaults to None.
            orientation (str, optional): 方向. Defaults to horizontal. 
                horizontal：水平标题行
                vertical：垂直标题行
                all： 全部
        Returns:
            None
        """
        if orientation == "vertical":
            self.verticalHeader().setHighlightSections(highlight)
        elif orientation == "horizontal":   
            self.horizontalHeader().setHighlightSections(highlight)
        else:
            self.horizontalHeader().setHighlightSections(highlight)
            self.verticalHeader().setHighlightSections(highlight)
    def _set_header_visible(self, visible:bool=False, orientation:str="horizontal") -> None:
        """设置是否显示水平标题
        Args:
            visible (bool, optional): _description_. Defaults to False.
            orientation (str, optional): 方向. Defaults to horizontal. 
                horizontal：水平标题行
                vertical：垂直标题行
                all： 全部
        Returns:
            None
        """
        if orientation == "vertical":
            self.verticalHeader().setVisible(visible)
        elif orientation == "horizontal":   
            self.horizontalHeader().setVisible(visible)
        else:
            self.verticalHeader().setVisible(visible)
            self.horizontalHeader().setVisible(visible)
    def _get_section_resize_mode(self, mode:str=None) -> QtWidgets:
        """获取调整模式
        Args:
            mode (str, optional): . Defaults to None.
        Returns:
            QtWidgets: _description_
        """
        if mode == "stretch":
            return QtWidgets.QHeaderView.Stretch
        elif mode == "fixed":
            return QtWidgets.QHeaderView.Fixed
        elif mode == "auto":
            return QtWidgets.QHeaderView.ResizeToContents
        elif mode == "interactive":
            return QtWidgets.QHeaderView.Interactive
    def _set_header_resize(self, mode:str=None, orientation:str="horizontal") -> None:
        """设置表格列标题89098i的调整模式
        Args:
            mode (str, optional): _description_. Defaults to None.
        Returns:
            None
        """
        mode = self._get_section_resize_mode(mode)
        if orientation == "vertical":
            self.verticalHeader().setSectionResizeMode(mode)
        elif orientation == "horizontal":   
            self.horizontalHeader().setSectionResizeMode(mode)
        else:
            self.verticalHeader().setSectionResizeMode(mode)
            self.horizontalHeader().setSectionResizeMode(mode)
    def _set_header_labels(self, labels:list=[]) -> None:
        """设置列头
        Args:
            labels (list, optional): 列头信息列表. Defaults to [].
        Returns:
            None
        """
        self.setHorizontalHeaderLabels(labels)
    # body 设置 ========================================
    def _set_col_count(self, num:int=0) -> None:
        """设置列数
        Args:
            num (int, optional): 列数. Defaults to 0.
        Returns:
            None
        """
        self.setColumnCount(num)
    def _set_row_count(self, num:int=0) -> None:
        """设置行数
        Args:
            num (int, optional): 行数. Defaults to 0.
        Returns:
            None
        """
        self.setRowCount(num)
    def _set_word_wrap(self, wrap:bool=False) -> None:
        """设置是否自动换行
        Args:
            wrap (bool, optional): 是否开启内容自动换行. Defaults to False.
        Returns:
            None
        """
        self.setWordWrap(wrap)
    def _set_cell_widget(self, row:int, col:int, widget:QtWidgets) -> None:
        """设置单元格的widget
        Args:
            row (int): 行
            col (int): 列
            widget (QtWidgets): widget
        Returns:
            None
        """
        self.setCellWidget(row, col, widget)
    # 其他设置 ========================================
    def _set_show_grid(self, show:bool=True) -> None:
        """设置是否显示网格
        Args:
            show (bool, optional): 是否显示网格. Defaults to True.
        Returns:
            None
        """
        self.setShowGrid(show)
    def _resize_by_contents(self, orientation:str="vertical") -> None:
        """设置自适应内容
        Args:
            orientation (str, optional): 方向. Defaults to "vertical".
                horizontal：水平
                vertical：垂直
                all：全部
        Returns:
            None
        """
        if orientation == "horizontal":
            self.resizeRowsToContents()
        elif orientation == "vertical":
            self.resizeColumnsToContents()
        elif orientation == "all":
            self.resizeColumnsToContents()
            self.resizeRowsToContents()
    def _clear_contents(self) -> None:
        """清除表格内容
        Returns:
            None
        """
        self.clearContents()  # 清理单元格内容
        self.setRowCount(0)
        self.setColumnCount(0)
    def _table_height_by_rows(self, row:int=3) -> None:
        """根据行数设置表格高度
        Args:
            row (int, optional): 行数. Defaults to 0.
        Returns:
            None
        """
        # height = 100
        height = 0
        headerHeight = self.horizontalHeader().height()
        rowCount = self.rowCount()
        for i in range(row):
            if i >= rowCount:
                height += self.rowHeight(rowCount-1) # 超过了则以最后一行为基准 
            else:
                
                height += self.rowHeight(i)
        self.setFixedHeight(height + headerHeight)

        
class DataTable(BaseTable):
    """报告表不允许编辑 编辑通过弹出式界面处理
    > 通过配置创建表格的表头以及数据映射的关系
    > 各行数据通过查找与表头匹配的项进行填充
    > 通过配置设置表头的显示与否
    > 通过配置设置表头的排序方式
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__editKey = None   # 记录弹出编辑对话框时的唯一key
        self.__editItems = {}   # 记录编辑对话框的配置信息
        self.__cacheDb = CacheDb() # 缓存数据 注意缓存数据 缓存的是 uuid:{} 的形式 然后在表格中每一行都会记录uuid 这样既可以快速查找，删除，排序等顺序行为也不会 
        self.__keyUserRole = None  # 记录uuid的列所在key 注意是对应key值，因为可能存在 换列或者后续隐藏列的情况，因此 定义key 比较好， 且此key 被定义则不允许隐藏或者删除
        self.__keyCols = {}     # 记录key和列的对应关系
    def _enable_default_config(self, params:dict={}) -> None:
        """启用默认配置
        Args:
            params (dict, optional): 配置参数. Defaults to {}.
        Returns:
            None
        """
        # 选择行
        self._set_selection_behavior("rows")
        # 单选
        self._set_selection_mode("single")
        # 不允许编辑
        self._set_edit_triggers() 
        # 设置表头 水平方向拉伸 垂直方向适应内容
        self._set_header_resize("stretch", "horizontal")

        rowDefaultHeight = params.get("rowDefaultHeight", None)
        if rowDefaultHeight:
            self.verticalHeader().setDefaultSectionSize(rowDefaultHeight)
        else:
            self._set_header_resize("auto", "vertical") # 注意设置此值会导致行高设置无效
    def _set_headers(self, headers:list=[]) -> None:
        """设置表头
        Args:
            cols (list, optional): 表头信息. Defaults to [].
                [
                    {
                        "title":"名称",
                        "key":"name",
                        "type":"str",
                        "minWidth":100,
                    }
                ]
        Returns:
            None
        """
        labels = [item.get("title") for item in headers]
        # 构建key 和 列 关系索引
        self.__keyCols = {item.get("key"):index for index, item in enumerate(headers)}
        # 初始化列时 根据列的信息自动初始化 __editItems
        self.__editItems = [] 

        self._set_col_count(len(headers))
        self._set_header_labels(labels)
        for header in headers:
            # 检查是否存在不需要编辑的项（不断丰富）
            editable = header.get("editable", True)
            if editable :
                self.__editItems.append(self.__init_edit_param(header))
    def __init_edit_param(self, item:dict) -> dict:
        """初始化编辑参数
        Args:
            item (dict): 列信息
        Returns:
            dict: 编辑参数
        """
        editableParams = item.get("editableParams", {})
        key = item.get("key")
        title = item.get("title")
        type = item.get("type")
        return {
            "key": key  ,
            "title": title,
            "type": type or "text",
            #  后者会覆盖前者
            **editableParams,
        }
    def _set_data(self, data:list=[]) -> None:
        """设置数据 注意此数据会清空已有缓存数据 并完成数据的初始化
        Args:
            data (list, optional): 数据. Defaults to [].
        Returns:
            None
        """
        # 为数据构建缓存数据
        rowIds = self._init_cache(data)
        self.setRowCount(len(rowIds))
        for row, id in enumerate(rowIds):
            self._init_row_by_id(row, id)
    def _init_cache(self, data:list=[]) -> None:
        """初始化缓存数据
        Args:
            data (list, optional): 数据. Defaults to [].
        Returns:
            None
        """
        self.__cacheDb._clear()
        return self.__cacheDb._init_db(data)
    def _get_id_data(self, id:str=None) -> dict:
        """获取行数据
        Args:
            row (int): 行
        Returns:
            dict: 数据
        """
        data = self.__cacheDb._get(id)
        return data and copy.deepcopy(data) or {}
    def _clear_cache(self) -> None:
        """清除缓存数据
        Returns:
            None
        """
        self.__cacheDb._clear()
    def _init_row_by_id(self, row:int, id:str) -> None:
        """设置行数据
        Args:
            row (int): 行
            id (str): id. Defaults to {}.
        Returns:
            None
        """
        data = self._get_id_data(id)
        self._set_row_show_text(row, data)
        col = self._get_userdata_col()
        self.item(row, col).setData(QtCore.Qt.UserRole, id)
    def _set_row_show_text(self, row, data:dict={}) -> None:
        """设置行展示的数据信息，根据列获取字段
        Args:
            row (int): 行
            data (dict, optional): 数据. Defaults to {}.
        Returns:
            None
        """

        rowCount = self.rowCount()
        if row >= rowCount:
            self.setRowCount(row+1) # +1 是因为row 从0 开始 所以实际 上应该是 row+1 的长度
        colCount = self.columnCount()
        for col in range(colCount):
            key = self._get_col_key(col)
            widgetItem = self.item(row, col)
            if not widgetItem:
                widgetItem = QtWidgets.QTableWidgetItem()
                self.setItem(row, col, widgetItem)
            widgetItem.setText(str(data.get(key, "")))
        
    def _get_row_id(self, row:int) -> str:
        """获取行id
        Args:
            row (int): 行
        Returns:
            str: id
        """
        col = self._get_userdata_col()
        return self.item(row, col).data(QtCore.Qt.UserRole)
    def _get_row_data(self, row:int):
        """获取行数据
        Args:
            row (int): 行
        Returns:
            dict: 数据
        Description:
            1. 先获取uuid被绑定的列
            2. 获取uuid
            3. 根据id 获取数据
        """
        id = self._get_row_id(row)
        return id and self._get_id_data(id) or {}
    def _get_userdata_col(self) -> int:
        """获取字段对应的列
        Args:
            key (str, optional): 字段. Defaults to None.
        Returns:
            int: 列
        """
        col = 0
        if self.__keyUserRole:
            col = self._get_key_col(self.__keyUserRole)
        return col
    def _get_row_by_id(self, id:str=None) -> int:
        """根据id 获取指定行
        Args:
            id (str, optional): id. Defaults to None.
        Returns:
            int: 行
        """
        for row in range(self.rowCount()):
            if self._get_row_id(row) == id:
                return row
        return -1
    def _get_key_col(self, key:str=None) -> int:
        """获取字段对应的列
        Args:
            key (str, optional): 字段. Defaults to None.
        Returns:
            int: 列
        """
        return self.__keyCols.get(key)
    def _get_col_key(self, col:int) -> str:
        """获取列对应的字段
        Args:
            col (int): 列
        Returns:
            str: 字段
        """

        for key, value in self.__keyCols.items():
            if value == col:
                return key
    def _enable_edit_dialog(self, editKey:str=None) -> None:
        """启用编辑对话框
        Args:
            None
        Returns:
            None
        """
        if not editKey:
            raise ValueError("_enable_edit_dialog(key) 参数key不能为空!!!")
        self.__editKey = editKey
        self.cellDoubleClicked.connect(self.__on_dbclick)
    def __on_dbclick(self, row:int, col:int) -> None:
        """双击事件 默认双击弹出编辑对话框
        Args:
            row (int): 行
            col (int): 列
        Returns:
            None
        """
        #  双击弹出规范化的编辑对话框界面
        #  通过配置文件生成对应的编辑界面
        data = {}
        rowId = self._get_row_id(row)
        data = self._get_row_data(row)
        key =  self.__editKey
        uiParams = {
            "key":key,
            
            "title": "修改",
            "items": self.__editItems,
        }
        global SHOW_DLGS
        dlg = None
        if not key in SHOW_DLGS:
            dlg =_PopEditDlg(uiParams, self)
            dlg.onSigSave.connect(self.__on_save)
        else:
            dlg = SHOW_DLGS[key]
        dlg and dlg._init(data, {
            "rowId":rowId,
        })
    def __on_save(self, info:dict={}, initConfig:dict={}) -> None:
        """自定义弹窗保存
        Args:
            info (dict, optional): _description_. Defaults to {}.
                data 
                delKeys
            initConfig (dict, optional): _description_. Defaults to {}.
                rowId
        """
        data = info.get("data")
        delKeys = info.get("delKeys")
        rowId = initConfig.get("rowId")
        oldData = self._get_id_data(rowId)
        for key in delKeys:
            oldData.pop(key)
        oldData.update(data) 
        self.__cacheDb._set(rowId, oldData)

        # 更新指定位置的显示内容
        row = self._get_row_by_id(rowId)
        self._set_row_show_text(row, oldData)

class FrozenTable(BaseTable):
    """冻结表 注意一旦使用冻结表则无法使用标题行和列
    > 通过模板生成数据展示表
    > 行冻结
    > 列冻结
    > 被冻结的列当作表头 禁止修改
    对外提供接口
        set_frozen_count(row, col) 设置冻结行列数
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.frozenColumnTableWidget = BaseTable(self)#冻结列
        self.frozenColumnTableWidget.hide()#先隐藏
        # self.frozenColumnTableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        
        self.frozenColumnCount=0
        self.frozenRowTableWidget = BaseTable(self)#冻结行
        self.frozenRowTableWidget.hide()#先隐藏
        
        self.frozenRowCount=0
        self.frozenLeftTopTableWidget = BaseTable(self)#左上角
        self.frozenLeftTopTableWidget.hide()#先隐藏

        # 不显示序号列 这个不能省略 否则数据对不齐
        self.verticalHeader().setVisible(False)
        
    # 私有函数
    def __set_row_count(self, frozenRowCount:int=0) -> None:
        tableRowCount = self.rowCount()
        if frozenRowCount > tableRowCount:
            frozenRowCount = tableRowCount 
        self.frozenRowCount = frozenRowCount if frozenRowCount > 0 else 0
        if self.frozenRowCount > 0:
            self.__init_row_table()
    def __set_column_count(self, frozenColumnCount:int=0) -> None:
        tableColCount = self.columnCount()
        if frozenColumnCount > tableColCount:
            frozenColumnCount = tableColCount 
        self.frozenColumnCount = frozenColumnCount if frozenColumnCount > 0 else 0
        if self.frozenColumnCount > 0:
            self.__init_column_table()
    def __init_row_table(self) -> None:
        self.frozenRowTableWidget.setRowCount(self.frozenRowCount)#只复制冻结列数据
        self.frozenRowTableWidget.setColumnCount(self.columnCount())
        for row in range(self.frozenRowCount):#设置行高
            for col in range(self.columnCount()):
                if self.item(row, col)!=None:#复制item #不好复制cellwidget
                    item=QtWidgets.QTableWidgetItem()
                    item.setText(self.item(row, col).text())
                    item.setFlags(self.item(row, col).flags())
                    # item.setBackground(self.item(row, col).background())
                    item.setTextAlignment(self.item(row, col).textAlignment())
                    item.setBackground(QtGui.QColor("lightblue"))
                    item.setFont(QtGui.QFont("Arial", 10, QtGui.QFont.Bold))

                    self.frozenRowTableWidget.setItem(row, col,item)
                self.frozenRowTableWidget.setColumnWidth(col, self.columnWidth(col) )
            self.frozenRowTableWidget.setRowHeight(row, self.rowHeight(row) )
        self.frozenRowTableWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
        self.frozenRowTableWidget.verticalHeader().hide()#都不要表头
        self.frozenRowTableWidget.horizontalHeader().hide()
        self.horizontalHeader().hide()
        self.verticalHeader().hide()
        # self.viewport().stackUnder(self.frozenRowTableWidget)
        # 设置无边框
        self.frozenRowTableWidget.setFrameStyle(QtWidgets.QFrame.Shape.NoFrame | QtWidgets.QFrame.Shadow.Plain)
        # 不显示滚动条
        self.frozenRowTableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.frozenRowTableWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.frozenRowTableWidget.show()
        self.__update_row_table_geo()        
        self.horizontalHeader().sectionResized.connect(self.__update_hsection_width)
        self.verticalHeader().sectionResized.connect(self.__update_vsection_height)
        self.frozenRowTableWidget.verticalHeader().sectionResized.connect(self.__resize_vheader_section)##
        self.frozenRowTableWidget.horizontalScrollBar().valueChanged.connect(self.horizontalScrollBar().setValue)##水平滚动条
        self.horizontalScrollBar().valueChanged.connect(self.frozenRowTableWidget.horizontalScrollBar().setValue)
        
        self.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
        self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
        self.frozenRowTableWidget.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
        self.frozenRowTableWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
    def __init_column_table(self) -> None:
        self.frozenColumnTableWidget.setColumnCount(self.frozenColumnCount)#只复制冻结列数据
        self.frozenColumnTableWidget.setRowCount(self.rowCount())
        for col in range(self.frozenColumnCount):#设置列宽
            for row in range(self.rowCount()):
                if self.item(row, col)!=None:#复制item #不好复制cellwidget
                    item=QtWidgets.QTableWidgetItem()
                    item.setText(self.item(row, col).text())
                    item.setFlags(self.item(row, col).flags())
                    # item.setBackground(self.item(row, col).background())
                    item.setTextAlignment(self.item(row, col).textAlignment())
                    item.setBackground(QtGui.QColor("lightblue"))
                    item.setFont(QtGui.QFont("Arial", 10, QtGui.QFont.Bold))
                    self.frozenColumnTableWidget.setItem(row, col,item)
                self.frozenColumnTableWidget.setRowHeight(row, self.rowHeight(row))
            self.frozenColumnTableWidget.setColumnWidth(col, self.columnWidth(col))
        self.frozenColumnTableWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
        self.frozenColumnTableWidget.verticalHeader().hide()#都不要表头
        self.frozenColumnTableWidget.horizontalHeader().hide()
        self.horizontalHeader().hide()
        self.verticalHeader().hide()
        # self.viewport().stackUnder(self.frozenColumnTableWidget)
        # 设置无边框
        self.frozenColumnTableWidget.setFrameStyle(QtWidgets.QFrame.Shape.NoFrame | QtWidgets.QFrame.Shadow.Plain)
        # 不显示滚动条
        self.frozenColumnTableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.frozenColumnTableWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.frozenColumnTableWidget.show()
        self.__update_column_table_geo()        
        self.horizontalHeader().sectionResized.connect(self.__update_hsection_width)
        self.verticalHeader().sectionResized.connect(self.__update_vsection_height)
        self.frozenColumnTableWidget.horizontalHeader().sectionResized.connect(self.__resize_hheader_section)
        self.frozenColumnTableWidget.verticalScrollBar().valueChanged.connect(self.verticalScrollBar().setValue)##垂直滚动条
        self.verticalScrollBar().valueChanged.connect(self.frozenColumnTableWidget.verticalScrollBar().setValue)

        self.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
        self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
        self.frozenColumnTableWidget.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
        self.frozenColumnTableWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
    def __init_lt_table(self):#左上角公共表头
        self.frozenLeftTopTableWidget.setRowCount(self.frozenRowCount)
        self.frozenLeftTopTableWidget.setColumnCount(self.frozenColumnCount)
        for row in range(self.frozenRowCount):
            for col in range(self.frozenColumnCount):
                if self.item(row, col)!=None:#复制item #不好复制cellwidget
                    item=QtWidgets.QTableWidgetItem()
                    item.setText(self.item(row, col).text())
                    item.setFlags(self.item(row, col).flags())
                    item.setBackground(self.item(row, col).background())
                    item.setTextAlignment(self.item(row, col).textAlignment())
                    item.setBackground(QtGui.QColor("lightblue"))
                    item.setFont(QtGui.QFont("Arial", 10, QtGui.QFont.Bold))
                    self.frozenLeftTopTableWidget.setItem(row, col,item)
                self.frozenLeftTopTableWidget.setColumnWidth(col, self.columnWidth(col))
            self.frozenLeftTopTableWidget.setRowHeight(row, self.rowHeight(row))
        self.frozenLeftTopTableWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
        self.frozenLeftTopTableWidget.verticalHeader().hide()#都不要表头
        self.frozenLeftTopTableWidget.horizontalHeader().hide()
        self.horizontalHeader().hide()
        self.verticalHeader().hide()
        self.frozenLeftTopTableWidget.raise_()
        # 设置无边框
        # self.frozenLeftTopTableWidget.setFrameStyle(QtWidgets.QFrame.Shape.NoFrame | QtWidgets.QFrame.Shadow.Plain)
        # 不显示滚动条
        self.frozenLeftTopTableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.frozenLeftTopTableWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.frozenLeftTopTableWidget.show()
        self.__update_lt_table_geo()
    def __update_hsection_width(self, logicalIndex, oldSize, newSize):
        if self.frozenColumnCount>0:
            if logicalIndex < self.frozenColumnCount:
                self.frozenColumnTableWidget.setColumnWidth(logicalIndex, newSize)
                self.__update_column_table_geo()
        if self.frozenRowCount>0:
                self.frozenRowTableWidget.setColumnWidth(logicalIndex, newSize)
    def __update_vsection_height(self, logicalIndex, oldSize, newSize):
        if self.frozenRowCount>0:
            if logicalIndex < self.frozenRowCount:
                self.frozenRowTableWidget.setRowHeight(logicalIndex, newSize)
                self.__update_row_table_geo()
        if self.frozenColumnCount>0:
                self.frozenColumnTableWidget.setRowHeight(logicalIndex, newSize)
    def __resize_hheader_section(self, logicalIndex, oldSize, newSize):
        if self.frozenColumnCount>0:
            if logicalIndex < self.frozenColumnCount:
                self.setColumnWidth(logicalIndex, newSize)
    def __resize_vheader_section(self, logicalIndex, oldSize, newSize):
        if self.frozenRowCount>0:
            if logicalIndex < self.frozenRowCount:
                self.setRowHeight(logicalIndex, newSize)
    def __update_column_table_geo(self):
        if self.frozenColumnCount>0:
            x =  self.frameWidth()
            y = self.frameWidth()
            w = 0
            for col in range(self.frozenColumnCount):
                w += self.columnWidth(col)
            h = self.viewport().height()
            # 设置新位置和宽高
            self.frozenColumnTableWidget.setGeometry(x, y, w, h)
    def __update_row_table_geo(self):
        if self.frozenRowCount>0:
            x = self.frameWidth()
            y = self.frameWidth()
            w = self.viewport().width()
            h = 0
            for row in range(self.frozenRowCount):
                h += self.rowHeight(row)
            # 设置新位置和宽高
            self.frozenRowTableWidget.setGeometry(x, y, w, h)
    def __update_lt_table_geo(self):
        x = self.frameWidth()
        y = self.frameWidth()
        w = 0
        for col in range(self.frozenColumnCount):
            w += self.columnWidth(col)
        h = 0
        for row in range(self.frozenRowCount):
            h += self.rowHeight(row)
        # 设置新位置和宽高
        self.frozenLeftTopTableWidget.setGeometry(x, y, w, h)
    # table 函数or事件 重写区域 ========================================
    def resizeEvent(self, event):
        super().resizeEvent(event)
        if self.frozenColumnCount>0:
            self.__update_column_table_geo()
        if self.frozenRowCount>0:
            self.__update_row_table_geo()
        if self.frozenColumnCount>0 and self.frozenRowCount>0:
            self.__update_lt_table_geo()
    def setItem(self,row,col,item):
        if self.frozenRowCount>0 and row<self.frozenRowCount and \
            self.frozenColumnCount>0 and col<self.frozenColumnCount:
            self.frozenLeftTopTableWidget.setItem(row,col,item)
        elif self.frozenRowCount>0 and row<self.frozenRowCount:
            self.frozenRowTableWidget.setItem(row,col,item)
        elif self.frozenColumnCount>0 and col<self.frozenColumnCount:
            self.frozenColumnTableWidget.setItem(row,col,item)
        else:
            super().setItem(row,col,item)
    def item(self,row,col):
        if self.frozenRowCount>0 and row<self.frozenRowCount and \
            self.frozenColumnCount>0 and col<self.frozenColumnCount:
            return self.frozenLeftTopTableWidget.item(row,col)
        elif self.frozenRowCount>0 and row<self.frozenRowCount:
            return self.frozenRowTableWidget.item(row,col)
        elif self.frozenColumnCount>0 and col<self.frozenColumnCount:
            return self.frozenColumnTableWidget.item(row,col)
        else:
            return super().item(row,col)
    def setCellWidget(self,row,col,cellwidget):
        super().setCellWidget(row,col,cellwidget)
        if self.frozenColumnCount>0:
            self.frozenColumnTableWidget.setRowHeight(row,self.rowHeight(row))
            self.frozenColumnTableWidget.setColumnWidth(col,self.columnWidth(col))
        if self.frozenRowCount>0:
            self.frozenRowTableWidget.setRowHeight(row,self.rowHeight(row))
            self.frozenRowTableWidget.setColumnWidth(col,self.columnWidth(col))
    def setSpan(self,row,col,rows,cols):        
        if self.frozenColumnCount>0 and col+cols<=self.frozenColumnCount+1 and \
            self.frozenRowCount>0 and row+rows<=self.frozenRowCount+1:
            self.frozenLeftTopTableWidget.setSpan(row,col,rows,cols)
        elif self.frozenColumnCount>0 and col+cols<=self.frozenColumnCount+1:
            self.frozenColumnTableWidget.setSpan(row,col,rows,cols)
        elif self.frozenRowCount>0 and row+rows<=self.frozenRowCount+1:
            self.frozenRowTableWidget.setSpan(row,col,rows,cols)
        elif row>=self.frozenRowCount and col>=self.frozenColumnCount and \
            row+rows<self.rowCount() and col+cols<self.columnCount():
            super().setSpan(row,col,rows,cols)
    def setRowHeight(self,row,height):
        super().setRowHeight(row,height)
        if self.frozenColumnCount>0 :
            self.frozenColumnTableWidget.setRowHeight(row,height)
        if self.frozenRowCount>0 and row<self.frozenRowCount:
            self.frozenRowTableWidget.setRowHeight(row,height)
            if self.frozenColumnCount>0 :
                self.frozenLeftTopTableWidget.setRowHeight(row,height)
    def setColumnWidth(self,col,width):
        super().setColumnWidth(col,width)
        if self.frozenRowCount>0:
            self.frozenRowTableWidget.setColumnWidth(col,width)
        if self.frozenColumnCount>0 and col<self.frozenColumnCount:
            self.frozenColumnTableWidget.setColumnWidth(col,width)
            if self.frozenRowCount>0:
                self.frozenLeftTopTableWidget.setColumnWidth(col,width)
    def setRowCount(self,count):
        lastRowCount=self.rowCount()
        if count!=lastRowCount:
            super().setRowCount(count)
            self.frozenColumnTableWidget.setRowCount(count)
    def _set_selection_behavior(self,  behavior:str=None) -> None:
        super()._set_selection_behavior(behavior)
        self.frozenRowTableWidget._set_selection_behavior(behavior)
        self.frozenColumnTableWidget._set_selection_behavior(behavior)
    def _set_edit_triggers(self, triggers:str=None) -> None:
        super()._set_edit_triggers(triggers)
        self.frozenColumnTableWidget._set_edit_triggers(triggers)
        self.frozenRowTableWidget._set_edit_triggers(triggers)
        self.frozenLeftTopTableWidget._set_edit_triggers(triggers)
    # 对外暴漏自定义接口区域 ========================================       
    def setColumnCount(self,count):
        lastColumnCount=self.columnCount()
        if count!=lastColumnCount:
            super().setColumnCount(count)
            self.frozenRowTableWidget.setColumnCount(count)
    
    def _set_frozen_rc_count(self, row:int=0, col:int=0) -> None:
        self.__set_row_count(row)
        self.__set_column_count(col)
        if self.frozenRowCount > 0 and self.frozenColumnCount > 0:
            self.__init_lt_table()#初始化左上角
    def _init_frozen_theme(self, headerRow=True, frozenRow=True, headerCol=True, frozenCol=True) -> None:
        """初始化冻结行和列的主题 ，注意一般数据更新后处理
        Args:
            theme (str, optional): _description_. Defaults to "dark".
            hasHeader (bool, optional): hasHeader 表示. Defaults to 0.
        background-color:#455364;
        border-bottom: 1px solid #19232d;   /* 边框颜色设置 */
        """
        # self.frozenRowTableWidget.setBackground(QtGui.QColor(69, 83, 100))
        # self.frozenLeftTopTableWidget.setStyleSheet(style)
        # self.frozenColumnTableWidget.setStyleSheet(style)
        
        style = """
            QTableWidget::item {
                border:transparent;
            }
        """
        # self.frozenLeftTopTableWidget.setStyleSheet(style)
        # self.frozenRowTableWidget.setStyleSheet(style)
        # self.frozenColumnTableWidget.setStyleSheet(style)

        # 目前未发现如何通过 stylesheet 设置第一行的样式
        # hasHeader 值如果为True 则认为第一行为实际表头行 会有特殊样式
        colNum = self.columnCount()

        rowCount = self.frozenRowTableWidget.rowCount()
        for row in range(rowCount):
            for col in range(colNum):
                if row == 0 and headerRow:
                    item = self.item(row,  col)
                    self.__init_theme(item, "headerRow")
                    # icon = QIcon("icon.png")
                    # item = QTableWidgetItem()
                    # item.setIcon(icon)
                elif row > 0  and frozenRow:
                    item = self.item(row,  col)
                    self.__init_theme(item, "frozenRow")

    def __init_theme(self, item:QtWidgets.QTableWidgetItem, curType:str=None) -> None:
        if curType == None:return 
        if curType == "headerRow":
            font = QtGui.QFont()
            font.setBold(True)
            item.setFont(font)
            item.setTextAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter)
            item.setBackground(QtGui.QColor("#455364")) 
            # 设置排序图标
            item.setIcon(QtGui.QIcon("sort.png"))
        elif curType == "frozenRow":
            item.setBackground(QtGui.QColor("#45536430"))
class _PopEditDlg(QtWidgets.QDialog):
    onSigSave = QtCore.pyqtSignal(dict, dict)
    def __init__(self, uiParams:dict={}, parent:QtWidgets.QWidget=None):
        super().__init__(parent)
        self.__key = uiParams.get('key')
        self.__initConfig = None
        SHOW_DLGS[self.__key] = self
        uiConfig = {
            "removeEmpty": True, # 移除空的项
            "title": uiParams.get("title", " "), # window title 
            "icon": uiParams.get("icon.png", "") , # window icon
            "objectName": uiParams.get("objectName", ""), # window objectName
            "layout":uiParams.get("layout", { # window 主布局
                "margin": [10, 10, 10, 10], # window 主布局的margin
                "space": 10,  # window 主布局的 Space
                "objectName": "qLayout", # window 主布局的 objectName
            }),
            "items":[
                *uiParams.get("items", []), # 通过items来传递参数
                {},
                {
                    "type": "tools",
                    "typeParams": { # 不同的类型所需要的参数不同，由typeParams来传递
                        "items": [
                            {
                                "title": "确定",
                                "action": lambda: self.__on_save(),
                            },
                            {
                                "title": "取消",
                                "action":lambda: self.reject(),
                            }
                        ],
                    },
                    "styleParams":{ # 控制样式的参数
                        "margin": [0, 0, 0, 0],
                        "space": 10,
                    }
                },
            ]
        }
        self.__ui = UiCustomForm(uiConfig, parent=parent)
        self.__ui._setup_ui(self)
        data = uiParams.get("data", {})
        if data:
            self._init(data)
        self.adjustSize()
        # 重新启用信号
        self.finished.connect(self.closeDialog)
        self.show() 
    def _init(self, data:dict={}, config:str=None) -> None:
        self.__initConfig = config 
        self.__ui._sets(data)
    def __on_save(self):
        info = self.__ui._gets()
        self.onSigSave.emit(info, self.__initConfig)
        self.accept()
        # 将修改后的数据缓存
        
    def closeDialog(self):
        global SHOW_DLGS
        if self.__key in SHOW_DLGS:
            del SHOW_DLGS[self.__key]
        # 禁用所有信号 关闭对话框后再次打开时会出现问题
        self.disconnect()

if __name__ == '__main__':
    def test_report_table():
        tableWidget = DataTable()
        tableWidget._enable_default_config({
            # "rowDefaultHeight":60  #  不设置默认行高则取默认自动
        })
        # 设置表头
        tableWidget._set_headers([
            {
                "title":"名称",
                "key":"name",
                # "editable":True, # 开关是否可编辑 缺省可编辑 值为True,注意此编辑对应的弹出对话框 而不是表格自身是否允许编辑
                "editableParams":{ # 可编辑时的参数 editable：True 时有效
                    "title":"材料名称",# 有则优先取用 没有则使用表格设置  需要注意的是如果不同则可能造成无法动态更新表格信息
                    "type":"text",
                    "must":True,
                    "typeParams": { # 不同的类型所需要的参数不同，由typeParams来传递
                        
                    },
                    "styleParams":{ # 控制样式的参数
                        "margin": [0, 0, 0, 0],
                        "labWidth": 100,
                        "space": 10,
                        "minWidth": 400,
                    }
                }
            },
            {
                "title":"类型",
                "key":"value",
                "editableParams":{ # 可编辑时的参数 editable：True 时有效
                    "type":"float",
                    "must":True,
                    "typeParams": { # 不同的类型所需要的参数不同，由typeParams来传递
                    },
                    "styleParams":{ # 控制样式的参数
                        "margin": [0, 0, 0, 0],
                        "labWidth": 100,
                        "space": 10,
                        "minWidth": 400,
                    }
                }
            },
            {
                "title":"类型2",
                "key":"value2",
                "editableParams":{ # 可编辑时的参数 editable：True 时有效
                    "type":"float",
                    "typeParams": { # 不同的类型所需要的参数不同，由typeParams来传递
                    },
                    "styleParams":{ # 控制样式的参数
                        "margin": [0, 0, 0, 0],
                        "labWidth": 100,
                        "space": 10,
                        "minWidth": 400,
                    }
                }
            }
        ])
        # 启用编辑事件 双击进入编辑界面
        gid = uuid.uuid4()
        tableWidget._enable_edit_dialog(gid)
        data = [
            {"name":"材料1", "value":1.0, "value2":2.0},
            {"name":"材料2", "value":2.0, "value2":3.0},
            {"name":"材料3", "value":3.0, "value2":4.0},
            {"name":"材料4", "value":4.0, "value2":5.0},
        ]
        tableWidget._set_data(data)

        tableWidget._table_height_by_rows(100)
        return tableWidget
    def test_frozen_table():
        tableWidget = FrozenTable()
        tableWidget._set_edit_triggers()
        tableWidget._set_selection_behavior("rows")
        tableWidget.setRowCount(50)
        tableWidget.setColumnCount(20)
        tableWidget._set_frozen_rc_count(3,3)#冻结 行数 列数
        tableWidget.setWindowTitle("QTableWidget冻结多行多列当做表头完美示例")
        tableWidget.resize(800, 700)
        
        tableWidget.setSortingEnabled(True)
        tableWidget.verticalHeader().hide()
        for row in range(tableWidget.rowCount()):
            for col in range(tableWidget.columnCount()):
                if row <3 or col<3:
                    item=QtWidgets.QTableWidgetItem("冻结(%d,%d)" % (row,col) )
                    # item.setBackground(QtGui.QColor("#eeeeee"))
                else:
                    item=QtWidgets.QTableWidgetItem("内容%d,%d" % (row,col) )
                    # item.setBackground(QtGui.QColor("#ffffff"))
                # item.setTextAlignment(QtCore.Qt.AlignCenter)
                tableWidget.setItem(row,col,item)
        
        tableWidget.setSpan(0,0,3,3)    ;tableWidget.item(0,0).setText("AA")
        tableWidget.setSpan(4,1,3,2)    ;tableWidget.item(4,1).setText("BB")
        tableWidget.setSpan(1,5,2,3)    ;tableWidget.item(1,5).setText("CC")
        tableWidget.setSpan(3,3,5,5)    ;tableWidget.item(3,3).setText("DD")
        tableWidget._init_frozen_theme()
        return tableWidget
    # 测试例子
    app = QtWidgets.QApplication(sys.argv)
    tableWidget= test_report_table()
    # tableWidget = test_frozen_table()
    tableWidget.resize(800, 800)
    tableWidget.show()
    
    # tableWidget.adjustSize()
    sys.exit(app.exec())